/*=============================================================================

  Library: CFS

  Copyright (c) German Cancer Research Center,
    Division of Medical and Biological Informatics

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

=============================================================================*/

#include "CFSPlugin_p.h"
#include "CFSPluginConstants.h"
#include "CFSPluginDatabaseException.h"
#include "CFSPluginArchive_p.h"
#include "CFSPluginFrameworkContext_p.h"
#include "CFSPluginFrameworkUtil_p.h"
#include "CFSPluginActivator.h"
#include "CFSPluginContext_p.h"

#include "CFSServiceReference_p.h"
#include "CFSServiceRegistration.h"
#include "CFSServices_p.h"

// for CFS::msecsTo() - remove after switching to Qt 4.7
#include <CFSUtils.h>

#include <typeinfo>

const CFSPlugin::States CFSPluginPrivate::RESOLVED_FLAGS = CFSPlugin::RESOLVED | CFSPlugin::STARTING | CFSPlugin::ACTIVE | CFSPlugin::STOPPING;

//----------------------------------------------------------------------------
void CFSPluginPrivate::LockObject::lock()
{
  m_Lock.lock();
}

//----------------------------------------------------------------------------
bool CFSPluginPrivate::LockObject::tryLock()
{
  return m_Lock.tryLock();
}

//----------------------------------------------------------------------------
bool CFSPluginPrivate::LockObject::tryLock(int timeout)
{
  return m_Lock.tryLock(timeout);
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::LockObject::unlock()
{
  m_Lock.unlock();
}

//----------------------------------------------------------------------------
bool CFSPluginPrivate::LockObject::wait(unsigned long time)
{
  return m_Condition.wait(&m_Lock, time);
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::LockObject::wakeAll()
{
  m_Condition.wakeAll();
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::LockObject::wakeOne()
{
  m_Condition.wakeOne();
}

//----------------------------------------------------------------------------
CFSPluginPrivate::CFSPluginPrivate(
    QWeakPointer<CFSPlugin> qq,
    CFSPluginFrameworkContext* fw,
    QSharedPointer<CFSPluginArchive> pa)
      : q_ptr(qq), fwCtx(fw), id(pa->getPluginId()),
      location(pa->getPluginLocation().toString()), state(CFSPlugin::INSTALLED),
      archive(pa), pluginContext(0), pluginActivator(0), pluginLoader(pa->getLibLocation()),
      resolveFailException(0), eagerActivation(false), wasStarted(false)
{
  //TODO
  //checkCertificates(pa);

  // Get library load hints
  if (fw->props.contains(CFSPluginConstants::FRAMEWORK_PLUGIN_LOAD_HINTS))
  {
    QVariant loadHintsVariant = fw->props[CFSPluginConstants::FRAMEWORK_PLUGIN_LOAD_HINTS];
    if (loadHintsVariant.isValid())
    {
      QLibrary::LoadHints loadHints = loadHintsVariant.value<QLibrary::LoadHints>();
      pluginLoader.setLoadHints(loadHints);
    }
  }

  checkManifestHeaders();

  pluginDir = fwCtx->getDataStorage(id);
//  int oldStartLevel = archive->getStartLevel();
  try
  {
    //TODO: StartLevel Service
    //if (fwCtx->startLevelController == 0)
    //{
      archive->setStartLevel(0);
    //}
//    else
//    {
//      if (oldStartLevel == -1)
//      {
//        archive->setStartLevel(fwCtx->startLevelController->getInitialPluginStartLevel());
//      }
//    }
  }
  catch (const std::exception& e)
  {
    qDebug() << "Failed to set start level on #" << id << ":" << e.what();
  }

  lastModified = archive->getLastModified();
  if (lastModified.isNull())
  {
    modified();
  }

  // fill require list
  QString requireString = archive->getAttribute(CFSPluginConstants::REQUIRE_PLUGIN);
  QList<QMap<QString, QStringList> > requireList = CFSPluginFrameworkUtil::parseEntries(CFSPluginConstants::REQUIRE_PLUGIN,
                                                                                        requireString, true, true, false);
  QListIterator<QMap<QString, QStringList> > i(requireList);
  while (i.hasNext())
  {
    const QMap<QString, QStringList>& e = i.next();
    const QStringList& res = e.value(CFSPluginConstants::RESOLUTION_DIRECTIVE);
    const QStringList& version = e.value(CFSPluginConstants::PLUGIN_VERSION_ATTRIBUTE);
    CFSRequirePlugin* rp = new CFSRequirePlugin(this, e.value("$key").front(),
                                                res.empty() ? QString() : res.front(),
                                                version.empty() ? QString() : version.front());
    require.push_back(rp);
  }
}

//----------------------------------------------------------------------------
CFSPluginPrivate::CFSPluginPrivate(QWeakPointer<CFSPlugin> qq,
                                   CFSPluginFrameworkContext* fw,
                                   long id, const QString& loc, const QString& sym, const CFSVersion& ver)
                                     : q_ptr(qq), fwCtx(fw), id(id), location(loc), symbolicName(sym), version(ver),
                                       state(CFSPlugin::INSTALLED), archive(0), pluginContext(0),
                                       pluginActivator(0), resolveFailException(0),
                                       eagerActivation(false), wasStarted(false)
{
  modified();
}

//----------------------------------------------------------------------------
CFSPluginPrivate::~CFSPluginPrivate()
{
  qDeleteAll(require);
}

//----------------------------------------------------------------------------
CFSPlugin::State CFSPluginPrivate::getUpdatedState()
{
  if (state & CFSPlugin::INSTALLED)
  {
    Locker sync(&operationLock);
    getUpdatedState_unlocked();
  }
  return state;
}

//----------------------------------------------------------------------------
CFSPlugin::State CFSPluginPrivate::getUpdatedState_unlocked()
{
  if (state & CFSPlugin::INSTALLED)
  {
    try
    {
      if (state == CFSPlugin::INSTALLED)
      {
        operation.fetchAndStoreOrdered(RESOLVING);
        fwCtx->resolvePlugin(this);
        state = CFSPlugin::RESOLVED;
        // TODO plugin threading
        //bundleThread().bundleChanged(new BundleEvent(BundleEvent.RESOLVED, this));
        fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::RESOLVED, this->q_func()));
        operation.fetchAndStoreOrdered(IDLE);
      }
    }
    catch (const CFSPluginException& pe)
    {
      if (resolveFailException) delete resolveFailException;
      resolveFailException = new CFSPluginException(pe);
      this->fwCtx->listeners.frameworkError(this->q_func(), pe);
    }
  }
  return state;
}

//----------------------------------------------------------------------------
QFileInfo CFSPluginPrivate::getDataRoot()
{
  return pluginDir;
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::setStateInstalled(bool sendEvent)
{
  Locker sync(&operationLock);

  // Make sure that the context is invalid
  if (pluginContext != 0)
  {
    pluginContext->d_func()->invalidate();
    pluginContext.reset();
  }
  state = CFSPlugin::INSTALLED;
  if (sendEvent)
  {
    operation.fetchAndStoreOrdered(UNRESOLVING);
    // TODO: plugin thread
    //bundleThread().bundleChanged(new BundleEvent(BundleEvent.UNRESOLVED, this));
    fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::UNRESOLVED, this->q_func()));
  }
  operation.fetchAndStoreOrdered(IDLE);
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::purge()
{
  if (state == CFSPlugin::UNINSTALLED)
  {
    fwCtx->plugins->remove(location);
  }
//  Vector fix = oldGenerations;
//  if (fix != null) {
//    oldGenerations = null;
//    for (Iterator i = fix.iterator(); i.hasNext();) {
//      ((BundleGeneration)i.next()).purge(true);
//    }
//  }
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::setAutostartSetting(const CFSPlugin::StartOptions& setting) {
  try
  {
    if (archive)
    {
      archive->setAutostartSetting(setting);
    }
  }
  catch (const CFSPluginDatabaseException& e)
  {
    this->fwCtx->listeners.frameworkError(this->q_func(), e);
  }
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::ignoreAutostartSetting()
{
  try
  {
    if (archive)
    {
      archive->setAutostartSetting(-1);
    }
  }
  catch (const CFSPluginDatabaseException& e)
  {
    this->fwCtx->listeners.frameworkError(this->q_func(), e);
  }
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::modified()
{
  lastModified = QDateTime::currentDateTime();
  if (archive)
  {
    archive->setLastModified(lastModified);
  }
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::checkManifestHeaders()
{
  symbolicName = archive->getAttribute(CFSPluginConstants::PLUGIN_SYMBOLICNAME);

  if (symbolicName.isEmpty())
  {
    throw CFSInvalidArgumentException(QString("CFSPlugin has no symbolic name, location=") +
                                      location);
  }

  QString mpv = archive->getAttribute(CFSPluginConstants::PLUGIN_VERSION);
  if (!mpv.isEmpty())
  {
    try
    {
      version = CFSVersion(mpv);
    }
    catch (const std::exception& e)
    {
      throw CFSInvalidArgumentException(QString("CFSPlugin does not specify a valid ") +
                                        CFSPluginConstants::PLUGIN_VERSION + " header. Got exception: " + e.what());
    }
  }

  QSharedPointer<CFSPlugin> snp = fwCtx->plugins->getPlugin(symbolicName, version);
  // TBD! Should we allow update to same version?
  if (!snp.isNull() && snp->d_func() != this)
  {
    throw CFSInvalidArgumentException(QString("Plugin with same symbolic name and version is already installed (")
                                      + symbolicName + ", " + version.toString() + ")");
  }

  QString ap = archive->getAttribute(CFSPluginConstants::PLUGIN_ACTIVATIONPOLICY);
  if (CFSPluginConstants::ACTIVATION_EAGER == ap)
  {
    eagerActivation = true;
  }

}

//----------------------------------------------------------------------------
void CFSPluginPrivate::finalizeActivation()
{
  Locker sync(&operationLock);

  // 4: Resolve plugin (if needed)
  switch (getUpdatedState_unlocked())
  {
  case CFSPlugin::INSTALLED:
    Q_ASSERT_X(resolveFailException != 0, Q_FUNC_INFO, "no resolveFailException");
    throw CFSPluginException(*resolveFailException);
  case CFSPlugin::STARTING:
    if (operation.fetchAndAddOrdered(0) == ACTIVATING) return; // finalization already in progress.
    // Lazy activation; fall through to RESOLVED.
  case CFSPlugin::RESOLVED:
  {
    //6:
    state = CFSPlugin::STARTING;
    operation.fetchAndStoreOrdered(ACTIVATING);
    if (fwCtx->debug.lazy_activation)
    {
      qDebug() << "activating #" << this->id;
    }
    //7:
    if (!pluginContext)
    {
      pluginContext.reset(new CFSPluginContext(this));
    }
    // start dependencies
    startDependencies();
    //TODO plugin threading
    //CFSRuntimeException* e = bundleThread().callStart0(this);
    CFSRuntimeException* e = start0();
    operation.fetchAndStoreOrdered(IDLE);
    operationLock.wakeAll();
    if (e)
    {
      CFSRuntimeException re(*e);
      delete e;
      throw re;
    }
    break;
  }
  case CFSPlugin::ACTIVE:
    break;
  case CFSPlugin::STOPPING:
    // This happens if start is called from inside the CFSPluginActivator::stop method.
    // Don't allow it.
    throw CFSPluginException("start called from CFSPluginActivator::stop",
                             CFSPluginException::ACTIVATOR_ERROR);
  case CFSPlugin::UNINSTALLED:
    throw CFSIllegalStateException("CFSPlugin is in UNINSTALLED state");
  }
}

//----------------------------------------------------------------------------
const CFSRuntimeException* CFSPluginPrivate::stop0()
{
  wasStarted = state == CFSPlugin::ACTIVE;
  // 5:
  state = CFSPlugin::STOPPING;
  operation.fetchAndStoreOrdered(DEACTIVATING);
  // 6-13:
  // TODO plugin threading
  //const CFSRuntimeException* savedException = pluginThread().callStop1(this);
  const CFSRuntimeException* savedException = stop1();
  if (state != CFSPlugin::UNINSTALLED)
  {
    state = CFSPlugin::RESOLVED;
    // TODO plugin threading
    //bundleThread().bundleChanged(new BundleEvent(BundleEvent.STOPPED, this));
    fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::STOPPED, this->q_func()));

    operationLock.wakeAll();
    operation.fetchAndStoreOrdered(IDLE);
  }
  return savedException;
}

//----------------------------------------------------------------------------
const CFSRuntimeException* CFSPluginPrivate::stop1()
{
  const CFSRuntimeException* res = 0;

  //6:
  fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::STOPPING, q_func()));

  //7:
  if (wasStarted && pluginActivator)
  {
    try
    {
      pluginActivator->stop(pluginContext.data());
      if (state != CFSPlugin::STOPPING)
      {
        if (state == CFSPlugin::UNINSTALLED)
        {
          return new CFSIllegalStateException("Plug-in is uninstalled");
        }
        else
        {
          return new CFSIllegalStateException("Plug-in changed state because of refresh during stop");
        }
      }
    }
    catch (const CFSException& e)
    {
      res = new CFSPluginException("CFSPlugin::stop: PluginActivator stop failed",
                                   CFSPluginException::ACTIVATOR_ERROR, e);
    }
    catch (...)
    {
      res = new CFSPluginException("CFSPlugin::stop: PluginActivator stop failed",
                                   CFSPluginException::ACTIVATOR_ERROR);
    }
    pluginActivator = 0;
  }

  if (operation.fetchAndAddOrdered(0) == DEACTIVATING)
  {
    // Call hooks after we've called PluginActivator::stop(), but before we've
    // cleared all resources
    if (pluginContext)
    {
      // TODO service listener hooks
      //fwCtx->listeners.serviceListeners.hooksBundleStopped(this);

      //8-10:
      removePluginResources();
      pluginContext->d_func()->invalidate();
      pluginContext.reset();
    }
  }

  // This would unload the shared library and delete the activator if
  // there are no dependencies. However, objects from the plug-in might
  // have been created via C-function symbol lookups. Hence we cannot
  // safely unload the DLL. Maybe implement a in-DLL counter later
  // (http://stackoverflow.com/questions/460809/c-dll-unloading-issue and
  // http://boost.2283326.n4.nabble.com/shared-ptr-A-smarter-smart-pointer-proposal-for-dynamic-libraries-td2649749.html).
  // The activator itself will be delete during program termination
  // (by the QPluginLoader instance).
  //pluginLoader.unload();

  return res;
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::update0(const QUrl& updateLocation, bool wasActive)
{
  const bool wasResolved = state == CFSPlugin::RESOLVED;
  const int oldStartLevel = getStartLevel();
  QSharedPointer<CFSPluginArchive> newArchive;

  operation.fetchAndStoreOrdered(UPDATING);
  try
  {
    // New plugin as stream supplied?
    QUrl updateUrl(updateLocation);
    if (updateUrl.isEmpty())
    {
      // Try Plugin-UpdateLocation
      QString update = archive != 0 ? archive->getAttribute(CFSPluginConstants::PLUGIN_UPDATELOCATION) : QString();
      if (update.isEmpty())
      {
        // Take original location
        updateUrl = location;
      }
    }

    if(updateUrl.scheme() != "file")
    {
      QString msg = "Unsupported update URL:";
      msg += updateUrl.toString();
      throw CFSPluginException(msg);
    }

    newArchive = fwCtx->storage->updatePluginArchive(archive, updateUrl, updateUrl.toLocalFile());
    //checkCertificates(newArchive);
    checkManifestHeaders();
    newArchive->setStartLevel(oldStartLevel);
    fwCtx->storage->replacePluginArchive(archive, newArchive);
  }
  catch (const std::exception& e)
  {
    if (!newArchive.isNull())
    {
      newArchive->purge();
    }
    operation.fetchAndStoreOrdered(IDLE);
    if (wasActive)
    {
      try
      {
        this->q_func().data()->start();
      }
      catch (const CFSPluginException& pe)
      {
        fwCtx->listeners.frameworkError(this->q_func(), pe);
      }
    }
    try
    {
      const CFSPluginException& pe = dynamic_cast<const CFSPluginException&>(e);
      throw pe;
    }
    catch (std::bad_cast)
    {
      throw CFSPluginException(QString("Failed to get update plugin: ") + e.what(),
                               CFSPluginException::UNSPECIFIED);
    }
  }

  bool purgeOld = false;
  // TODO: check if dependent plug-ins are started. If not, set purgeOld to true.

  // Activate new plug-in
  QSharedPointer<CFSPluginArchive> oldArchive = archive;
  archive = newArchive;
  cachedRawHeaders.clear();
  state = CFSPlugin::INSTALLED;

  // Purge old archive
  if (purgeOld)
  {
    //secure.purge(this, oldProtectionDomain);
    if (oldArchive != 0)
    {
      oldArchive->purge();
    }
  }

  // Broadcast events
  if (wasResolved)
  {
    // TODO: use plugin threading
    //bundleThread().bundleChanged(new BundleEvent(BundleEvent.UNRESOLVED, this));
    fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::UNRESOLVED, this->q_func()));
  }
  //bundleThread().bundleChanged(new BundleEvent(BundleEvent.UPDATED, this));
  fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::UPDATED, this->q_func()));
  operation.fetchAndStoreOrdered(IDLE);

   // Restart plugin previously stopped in the operation
   if (wasActive)
   {
     try
     {
       this->q_func().data()->start();
     }
     catch (const CFSPluginException& pe)
     {
       fwCtx->listeners.frameworkError(this->q_func(), pe);
     }
   }
 }

//----------------------------------------------------------------------------
int CFSPluginPrivate::getStartLevel()
{
  if (archive != 0)
  {
    return archive->getStartLevel();
  }
  else
  {
    return 0;
  }
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::waitOnOperation(LockObject* lock, const QString& src, bool longWait)
{
  if (operation.fetchAndAddOrdered(0) != IDLE)
  {
    qint64 left = longWait ? 20000 : 500;
    QDateTime waitUntil = QDateTime::currentDateTime().addMSecs(left);
    do
    {
      lock->wait(left);
      if (operation.fetchAndAddOrdered(0) == IDLE)
      {
        return;
      }
      // TODO use Qt 4.7 QDateTime::msecsTo() API
      //left = QDateTime::currentDateTime().msecsTo(waitUntil);
      left = CFS::msecsTo(QDateTime::currentDateTime(), waitUntil);
    } while (left > 0);

    QString op;
    switch (operation.fetchAndAddOrdered(0))
    {
    case IDLE:
      // Should not happen!
      return;
    case ACTIVATING:
      op = "start";
      break;
    case DEACTIVATING:
      op = "stop";
      break;
    case RESOLVING:
      op = "resolve";
      break;
    case UNINSTALLING:
      op = "uninstall";
      break;
    case UNRESOLVING:
      op = "unresolve";
      break;
    case UPDATING:
      op = "update";
      break;
    default:
      op = "unknown operation";
      break;
    }
    throw CFSPluginException(src + " called during " + op + " of plug-in",
                             CFSPluginException::STATECHANGE_ERROR);
  }
}

//----------------------------------------------------------------------------
QStringList CFSPluginPrivate::findResourceEntries(const QString& path,
                                                  const QString& pattern, bool recurse) const
{
  QStringList result;
  QStringList resources = archive->findResourcesPath(path);
  foreach(QString fp, resources)
  {
    QString lastComponentOfPath = fp.section('/', -1);
    bool isDirectory = fp.endsWith("/");

    if (!isDirectory &&
        (pattern.isNull() || CFSPluginFrameworkUtil::filterMatch(pattern, lastComponentOfPath)))
    {
      result << (path + fp);
    }
    if (isDirectory && recurse)
    {
      QStringList subResources = findResourceEntries(fp, pattern, recurse);
      foreach (QString subResource, subResources)
      {
        result << (path + subResource);
      }
    }
  }
  return result;
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::startDependencies()
{
  QListIterator<CFSRequirePlugin*> i(this->require);
  while (i.hasNext())
  {
    CFSRequirePlugin* pr = i.next();
    QList<CFSPlugin*> pl = fwCtx->plugins->getPlugins(pr->name, pr->pluginRange);
    if (pl.isEmpty())
    {
      if (pr->resolution == CFSPluginConstants::RESOLUTION_MANDATORY)
      {
        // We should never get here, since the plugin can only be
        // started if all its dependencies could be resolved.
        throw CFSPluginException(
            QString("Internal error: dependent plugin %1 inside version range %2 is not installed.").
            arg(pr->name).arg(pr->pluginRange.toString()));
      }
      else
      {
        continue;
      }
    }

    // We take the first plugin in the list (highest version number)
    // Immediately start the dependencies (no lazy activation) but do not
    // change the autostart setting of the plugin.
    pl.front()->start(CFSPlugin::START_TRANSIENT);
  }
}

//----------------------------------------------------------------------------
CFSPluginException* CFSPluginPrivate::start0()
{
  CFSPluginException* res = 0;

  fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::STARTING, this->q_func()));

  CFSPluginException::Type error_type = CFSPluginException::MANIFEST_ERROR;
  try {
    pluginLoader.load();
    if (!pluginLoader.isLoaded())
    {
      error_type = CFSPluginException::ACTIVATOR_ERROR;
      throw CFSPluginException(QString("Loading plugin %1 failed: %2").arg(pluginLoader.fileName()).arg(pluginLoader.errorString()),
                               CFSPluginException::ACTIVATOR_ERROR);
    }

    pluginActivator = qobject_cast<CFSPluginActivator*>(pluginLoader.instance());
    if (!pluginActivator)
    {
      throw CFSPluginException(QString("Creating CFSPluginActivator instance from %1 failed: %2").arg(pluginLoader.fileName()).arg(pluginLoader.errorString()),
                               CFSPluginException::ACTIVATOR_ERROR);
    }

    pluginActivator->start(pluginContext.data());

    if (state != CFSPlugin::STARTING)
    {
      error_type = CFSPluginException::STATECHANGE_ERROR;
      if (CFSPlugin::UNINSTALLED == state)
      {
        throw CFSPluginException("CFSPlugin uninstalled during start()", CFSPluginException::STATECHANGE_ERROR);
      }
      else
      {
        throw CFSPluginException("CFSPlugin changed state because of refresh during start()", CFSPluginException::STATECHANGE_ERROR);
      }
    }
    state = CFSPlugin::ACTIVE;
  }
  catch (const CFSException& e)
  {
    res = new CFSPluginException("CFSPlugin start failed", error_type, e);
  }
  catch (...)
  {
    res = new CFSPluginException("CFSPlugin start failed", error_type);
  }

  if (fwCtx->debug.lazy_activation)
  {
    qDebug() << "activating #" << id << "completed.";
  }

  if (res == 0)
  {
    //10:
    fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::STARTED, this->q_func()));
  }
  else if (operation.fetchAndAddOrdered(0) == ACTIVATING)
  {
    // 8:
    state = CFSPlugin::STOPPING;
    fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::STOPPING, this->q_func()));
    removePluginResources();
    pluginContext->d_func()->invalidate();
    pluginContext.reset();
    state = CFSPlugin::RESOLVED;
    fwCtx->listeners.emitPluginChanged(CFSPluginEvent(CFSPluginEvent::STOPPED, this->q_func()));
  }

  return res;
}

//----------------------------------------------------------------------------
void CFSPluginPrivate::removePluginResources()
{
  // automatic disconnect due to Qt signal slot
  //fwCtx->listeners.removeAllListeners(this);

  QList<CFSServiceRegistration> srs = fwCtx->services->getRegisteredByPlugin(this);
  QMutableListIterator<CFSServiceRegistration> i(srs);
  while (i.hasNext())
  {
    try
    {
      i.next().unregister();
    }
    catch (const CFSIllegalStateException& /*ignore*/)
    {
      // Someone has unregistered the service after stop completed.
      // This should not occur, but we don't want get stuck in
      // an illegal state so we catch it.
    }
  }

  QList<CFSServiceRegistration> s = fwCtx->services->getUsedByPlugin(q_func());
  QListIterator<CFSServiceRegistration> i2(s);
  while (i2.hasNext())
  {
    i2.next().getReference().d_func()->ungetService(q_func(), false);
  }

}
